package com.ruoyi.flow.common;

import com.alibaba.druid.util.StringUtils;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.ruoyi.flow.flow.workflow.FlowTask;
import com.ruoyi.flow.flow.workflow.OrderRef;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.Lists;
import org.activiti.engine.query.Query;
import org.springframework.util.ReflectionUtils;

import javax.persistence.Transient;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.List;
import java.util.concurrent.ExecutionException;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkState;

/**
 * @Author: wtian
 * @Description:
 * @Date: Created in 14:21 2018/8/16
 * @Version: 1.0
 */
public class Page<T> {
    public static final String ORDER_DIRECTION_ASC = "asc";
    public static final String ORDER_DIRECTION_DESC = "desc";
    public static final String DEFAULT_SORT = "createTimeStr";
    public static final String DEFAULT_DIRECT = ORDER_DIRECTION_DESC;
    /**
     * 总数
     */
    private long total;
    /**
     * 内容
     */
    private List<T> rows;
    /**
     * 每页条数
     */
    private int limit = 10;

    /**
     * 从第几条开始计算
     */
    private int offset = -1;
    /**
     * 页数
     */
    private int pageNumber = -1;
    /**
     * 每页条数
     */
    private int pageSize = -1;
    /**
     * 排序字段
     */
    private String sort;

    public String getSort() {
        return sort;
    }

    public void setSort(String sort) {
        this.sort = sort;
    }

    /**
     * 排序方式 {@code asc} or {@code desc}
     */
    private String order = ORDER_DIRECTION_ASC;

    private String search;

    public long getTotal() {
        return total;
    }

    public void setTotal(long total) {
        this.total = total;
    }

    public List<T> getRows() {
        return rows;
    }

    public void setRows(List<T> rows) {
        this.rows = rows;
    }

    public int getLimit() {
        return limit;
    }

    public void setLimit(int limit) {
        this.limit = limit;
        this.pageSize = limit;
    }

    public int getPageNumber() {
        return pageNumber;
    }

    public void setPageNumber(int pageNumber) {
        this.pageNumber = pageNumber;
    }

    public int getPageSize() {
        return pageSize;
    }

    public void setPageSize(int pageSize) {
        this.pageSize = pageSize;
    }

    public String getOrder() {
        return order;
    }

    public void setOrder(String order) {
        checkArgument(StringUtils.equals(order, ORDER_DIRECTION_ASC) || StringUtils.equals(order, ORDER_DIRECTION_DESC), "order value must be asc or desc.");
        this.order = order;
    }

    public String getSearch() {
        return search;
    }

    public void setSearch(String search) {
        this.search = search;
    }

    public int getOffset() {
        return offset;
    }

    public void setOffset(int offset) {
        this.offset = offset;
        this.pageNumber = offset / this.limit + 1;
    }

    /**
     * 根据字段名从Activiti查询对象中找到对应的排序方法
     *
     * @param fieldName  flowTask类的字段，通过该字段的注解获取到activiti查询对应对应的排序方法
     * @param queryClass activiti查询对象，例如{@code HistoricProcessInstanceQuery}
     * @return
     */
    private Method getOrderMethodByFlowTaskField(String fieldName, Class queryClass) {
        final String key = fieldName + "#" + queryClass.getName();
        try {
            return queryMethodCache.get(key);
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 获取排序方向的方法
     *
     * @param direction
     * @param queryClass
     * @return
     */
    private Method getOrderDirectionMethod(String direction, Class queryClass) {
        String methodName = direction;
        return ReflectionUtils.findMethod(queryClass, methodName);
    }

    /**
     * 调用activiti对象的查询方法
     *
     * @param queryObject
      * @return
     */
    public <T extends Query> T executeSortAndOrderMethod(T queryObject) {
        T queryObjectInstance = queryObject;
        //设置默认排序
        if (org.apache.commons.lang3.StringUtils.isEmpty(getOrder()) || org.apache.commons.lang3.StringUtils.isEmpty(getSort())) {
            setOrder(DEFAULT_DIRECT);
            setSort(DEFAULT_SORT);
        }
        if (!org.apache.commons.lang3.StringUtils.isEmpty(getOrder()) && !org.apache.commons.lang3.StringUtils.isEmpty(getSort())) {
            // 调用排序字段方法
            Method sortMethod = getOrderMethodByFlowTaskField(getSort(), queryObject.getClass());
            queryObjectInstance = (T) ReflectionUtils.invokeMethod(sortMethod, queryObjectInstance);
            // 调用排序方向方法
            Method orderMethod = getOrderDirectionMethod(getOrder(), queryObject.getClass());
            queryObjectInstance = (T) ReflectionUtils.invokeMethod(orderMethod, queryObjectInstance);
        }
        return queryObjectInstance;
    }

    /**
     * 执行分页方法
     *
     * @param queryObject
     * @param <T>         查询对象
     * @param <R>         结果对象
     * @return
     */
    public <T extends Query, R> List<R> executePage(T queryObject, Class<R> clazz) {
        List<R> results = Lists.newLinkedList();
        if (getPageNumber() != -1 && getPageSize() != -1) {//分页
            setTotal(queryObject.count());
            results = queryObject.listPage(getOffset(), getLimit());
        } else {
            results = queryObject.list();
        }
        return results;
    }

    /**
     * activiti查询对象对应的排序方法缓存。
     * <br/>
     * key是flowTask的fieldName与Activity查询对象的classPath中间用#隔开的字符串
     * <br/>
     * value是查询对象对应的排序方法
     */
    @Transient
    @JsonIgnore
    private static LoadingCache<String, Method> queryMethodCache;

    static {
        queryMethodCache = CacheBuilder.<String, Method>newBuilder().recordStats().build(new CacheLoader<String, Method>() {
            @Override
            public Method load(String s) throws Exception {
                checkArgument(org.apache.commons.lang3.StringUtils.isNotEmpty(s), "key must not be value.");
                checkArgument(org.apache.commons.lang3.StringUtils.split(s, "#").length == 2, "key must split with #");
                String fieldName = org.apache.commons.lang3.StringUtils.substringBefore(s, "#");
                String classPath = org.apache.commons.lang3.StringUtils.substringAfter(s, "#");
                Class queryClass = Class.forName(classPath);
                return getMethod(fieldName, queryClass);
            }

            private Method getMethod(String fieldName, Class queryClass) {
                Field field = ReflectionUtils.findField(FlowTask.class, fieldName);
                if (field != null) {
                    OrderRef orderRef = field.getAnnotation(OrderRef.class);
                    if (orderRef != null) {
                        Class[] queryClasses = orderRef.queryClass();
                        String[] methodNames = orderRef.methodName();
                        checkState(queryClasses.length == methodNames.length, "查询对象与查询方法个数不一致");
                        for (int i = 0; i < queryClasses.length; i++) {
                            Class _queryClass = queryClasses[i];
                            if (_queryClass.isAssignableFrom(queryClass)) {
                                String methodName = methodNames[i];
                                Method queryMethod = ReflectionUtils.findMethod(queryClass, methodName);
                                return queryMethod;
                            }
                        }
                    }
                }
                return null;
            }
        });
    }
}
